package com.emm.yixun.common.utils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p></p>
 */
public class BeanUtil {

    private final static Logger logger = LoggerFactory.getLogger(BeanUtil.class);

    /** 
     * @Title: convert2T 
     * @Description: <p>全值复制 <p>
     * @param targetObj 目标对象
     * @param sourceObj 源始对象
     * @return  
     * @return T    返回类型 
     * @throws 
     */
    public static <T> T convert2T(T targetObj, Object sourceObj) {
        try {
            return copyProperties(targetObj, sourceObj);
        } catch (Exception e) {
            throw new RuntimeException("BeanUtils.convert2T error!" + e.getMessage(), e);
        }
    }

    /**
     * <p>简单封装下拷贝属性方法</p>
     * <p>将异常改为运行时异常抛出</p>
     * @param targetObj 目标类
     * @param sourceObj 源类
     * @param fieldPairs 指定的字段赋值，pair左侧是源字段名，右侧为对应的目标字段名
     */
    public static <T> T convert2T(T targetObj, Object sourceObj, Pair<String, String>... fieldPairs) {
        String[] fieldNames = new String[fieldPairs.length * 2];
        for (int i = 0; i < fieldPairs.length; i++) {
            Pair<String, String> fieldPair = fieldPairs[i];
            fieldNames[2 * i] = fieldPair.getLeft();
            fieldNames[2 * i + 1] = fieldPair.getRight();

        }
        return convert2T(targetObj, sourceObj, null, fieldNames);
    }

    /** 
     * <p> 根据标识和值修改给定对象中属性值 <p>
     * @Title convert2T 
     * @param targetObj
     * @param fieldPairs
     * @return T    返回类型 
     * @throws IntrospectionException 
     * @throws InvocationTargetException 
     * @throws IllegalAccessException 
     * @throws IllegalArgumentException 
     */
    public static <T> T fillInT(T targetObj, String splitSign, Pair<String, String>... fieldPairs) {
        if (targetObj == null) {
            throw new IllegalArgumentException(
                "BeanUtils.fillInT error !targetObj can not be null!");
        }
        if (ArrayUtils.isNotEmpty(fieldPairs)) {
            for (Pair<String, String> pair : fieldPairs) {
                try {
                    fillInT(pair, splitSign, targetObj);
                } catch (Exception e) {
                    logger.info("BeanUtils.fillInT error!" + e.getMessage(), e);
                    throw new RuntimeException("BeanUtils.convert2T error!" + e.getMessage(), e);
                }
            }
        }
        return targetObj;
    }

    @SuppressWarnings("rawtypes")
    private static <T> void fillInT(Pair<String, String> pair, String splitSign, T targetObj)
                                                                                             throws Exception {
        PropertyDescriptor setField = null;
        PropertyDescriptor getField = null;
        String[] fieldLevel = pair.getKey().split(splitSign);
        if (fieldLevel.length > 1) {
            //多层次
            Map lastMap = null;
            for (String string : fieldLevel) {
                getField = new PropertyDescriptor(string, targetObj.getClass());
                //给目标类属性赋值
                if (Map.class.isAssignableFrom(getField.getPropertyType())) {
                    setField = new PropertyDescriptor(string, targetObj.getClass());
                    Method setter = setField.getWriteMethod();
                    lastMap = new HashMap();
                    setter.invoke(targetObj, lastMap);
                } else {
                    lastMap.put(string, pair.getValue());
                }
            }
        } else {
            //单层次
            setField = new PropertyDescriptor(pair.getKey(), targetObj.getClass());
            Method setter = setField.getWriteMethod();
            //给目标类属性赋值
            setter.invoke(targetObj, pair.getValue());
        }
    }

    /**
     * <p>简单封装下拷贝属性方法</p>
     * <p>将异常改为运行时异常抛出</p>
     * @param targetObj 目标类
     * @param sourceObj 源类
     * @param fieldNames 指定的字段赋值，个数必须是偶数，且第一个是源字段名，第二个为对应的目标字段名
     * @deprecated 请使用 [com.qby.common.utils.BeanUtil.convert2T(T, Object, Pair<String, String>...)]
     */
    public static <T> T convert2T(T targetObj, Object sourceObj, String[] fieldNames) {
        return convert2T(targetObj, sourceObj, null, fieldNames);
    }

    /** 
     * @Title: convert2T 
     * @Description: <p>属性转换工具方法 <p>
     * @param targetObj 目标类
     * @param sourceObj 源类
     * @param assignFields 指定的字段赋值，个数必须是偶数，且第一个是源字段名，第二个为对应的目标字段名
     * @param ignoreFields 忽略字段
     * @return  
     * @return T    返回类型 
     * @throws 
     */
    public static <T> T convert2T(T targetObj, Object sourceObj, String[] ignoreFields,
                                  String[] assignFields) {
        try {
            if (targetObj == null || sourceObj == null) {
                throw new RuntimeException(
                    "BeanUtils.copyProperties error , targetObj or sourceObj is null !");
            }
            if (ArrayUtils.isEmpty(ignoreFields)) {
                convert4assign(targetObj, sourceObj, assignFields);
            } else {
                convert4ignore(targetObj, sourceObj, ignoreFields, assignFields);
            }
        } catch (Exception e) {
            logger.info("BeanUtils.copyProperties error!" + e.getMessage(), e);
            throw new RuntimeException("BeanUtils.copyProperties error!" + e.getMessage(), e);
        }
        return targetObj;
    }

    private static <T> T convert4assign(T targetObj, Object sourceObj, String... assignFields)
                                                                                              throws Exception {
        //无忽略字段
        //        BeanUtils.copyProperties(targetObj, sourceObj);
        copyProperties(targetObj, sourceObj);
        //没有特定指定具体字段映射则直接返回
        if (assignFields.length == 0 || assignFields.length % 2 > 0) {
            return targetObj;
        }
        //按照 先源字段后目标字段顺序映射字段
        for (int i = 0; i < assignFields.length; i++) {
            if (i % 2 == 0) {
                String getFieldName = assignFields[i], setFieldName = assignFields[i + 1];
                //得到字段的源类的读取方法  
                PropertyDescriptor getField = new PropertyDescriptor(getFieldName,
                    sourceObj.getClass());
                Method getter = getField.getReadMethod();
                //得到字段的目标类的写入方法  
                PropertyDescriptor setField = new PropertyDescriptor(setFieldName,
                    targetObj.getClass());
                Method setter = setField.getWriteMethod();
                //给目标类属性赋值
                setter.invoke(targetObj, getter.invoke(sourceObj));
            }
        }
        return targetObj;
    }

    private static <T> T convert4ignore(T targetObj, Object sourceObj, String[] ignoreFields,
                                        String... assignFields) throws Exception {
        //按照 先源字段后目标字段顺序映射字段
        BeanInfo beanInfoS = Introspector.getBeanInfo(sourceObj.getClass());
        PropertyDescriptor[] propertyDescriptorsS = beanInfoS.getPropertyDescriptors();
        List<String> ignoreFieldList = Arrays.asList(ignoreFields), assignFieldSList = new ArrayList<String>();

        if (assignFields != null && assignFields.length > 0) {
            for (int i = 0; i < assignFields.length; i++) {
                if (i % 2 == 0) {
                    assignFieldSList.add(assignFields[i]);
                }
            }
        }
        for (PropertyDescriptor property : propertyDescriptorsS) {
            String key = property.getName(), setFieldName = key;
            // 过滤 指定的字段名称 和 class属性以及serialVersionUID  
            if (ignoreFieldList.contains(key) || "class".equals(key)
                || "serialVersionUID".equals(key)) {
                continue;
            }
            if (assignFieldSList.contains(key)) {
                setFieldName = assignFields[(assignFieldSList.indexOf(key) * 2 + 1)];
            }
            //得到字段的源类的读取方法  
            Method getter = property.getReadMethod();
            //得到字段的目标类的写入方法  
            try {
                PropertyDescriptor setField = new PropertyDescriptor(setFieldName, targetObj.getClass());
                Method setter = setField.getWriteMethod();
                setter.invoke(targetObj, getter.invoke(sourceObj));
            } catch (IntrospectionException e) {
                logger.warn("ignore the exception :", e);
            }
            //给目标类属性赋值
        }
        return targetObj;
    }

    /** 
     * @Title: copyProperties 
     * @Description: <p>全值复制 <p>
     * @param targetObj 目标对象
     * @param sourceObj 源始对象
     * @return
     * @throws Exception  
     * @return T    返回类型  返回复制后的目标对象
     * @throws Exception 
     * @throws 
     */
    private static <T> T copyProperties(T targetObj, Object sourceObj) throws Exception {
        if (targetObj == null || sourceObj == null) {
            throw new RuntimeException(
                "convert error when copyProperties! " + targetObj == null ? "targetObj"
                    : "sourceObj" + " can not be null,but now is null!");
        }
        BeanInfo beanInfoS = Introspector.getBeanInfo(sourceObj.getClass());
        PropertyDescriptor[] propertyDescriptorsS = beanInfoS.getPropertyDescriptors();

        for (PropertyDescriptor property : propertyDescriptorsS) {
            String key = property.getName(), setFieldName = key;
            //得到字段的源类的读取方法  
            Method getter = property.getReadMethod();
            if (getter == null)
                continue;
            //得到字段的目标类的写入方法  
            PropertyDescriptor setField;
            try {
                //不存在该属性时忽略错误，继续寻找下一个字段
                setField = new PropertyDescriptor(setFieldName, targetObj.getClass());
            } catch (Exception e) {
                continue;
            }
            Method setter = setField.getWriteMethod();
            if (setter == null)
                continue;
            //给目标类属性赋值
            try {
                setter.invoke(targetObj, getter.invoke(sourceObj));
            } catch (Exception e) {
                logger.info("convert error ! fieldName:{},targetObj:{},sourceObj:{}", key,
                    targetObj.getClass(), sourceObj.getClass());
            }
        }
        return targetObj;
    }
}
